/*
    This file is part of QTau
    Copyright (C) 2013-2018  Tobias "Tomoko" Platen <tplaten@posteo.de>
    Copyright (C) 2013       digited       <https://github.com/digited>
    Copyright (C) 2010-2013  HAL@ShurabaP  <https://github.com/haruneko>

    QTau is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    SPDX-License-Identifier: GPL-3.0+
*/
#define __devloglevel__ 4

#include "jackaudio.h"
#include <assert.h>

#include <stdio.h>
#include <QDebug>
#include <QMessageBox>
#include <QIcon>

#include <sys/eventfd.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>

#include <jack/midiport.h>
#include <iostream>

int JackAudio::createEFD()
{
    _eventfd = eventfd(0, 0);
    return _eventfd;
}

int JackAudio::readMidiData(char *buffer, int maxlength)
{
    int count = jack_ringbuffer_read_space(this->_midi_rb);
    if(count>0)
    {
        if(count>maxlength) count=maxlength;
        jack_ringbuffer_read(this->_midi_rb,buffer,count);
    }
    return count;
}


int JackAudio::process(jack_nframes_t nframes, void *arg)
{

    JackAudio* conn = (JackAudio*) arg;

    // midi input handing
    void* port_buf = jack_port_get_buffer( conn->_midi_port, nframes);
    jack_nframes_t event_count = jack_midi_get_event_count(port_buf);
    if(event_count > 0)
    {
        for(jack_nframes_t i=0; i<event_count; i++)
        {
            jack_midi_event_t in_event;
            //jack_position_t position;
            jack_midi_event_get(&in_event, port_buf, i);
            //FIXME: sysex support
            jack_ringbuffer_write(conn->_midi_rb,(char*)in_event.buffer,in_event.size);
        }
    }


    // audio output
    sample_t* write_samp = (sample_t*)jack_port_get_buffer(conn->_write_port,nframes);
    if(jack_ringbuffer_read_space(conn->_write_rb)>=sizeof(sample_t)*nframes)
    {
        jack_ringbuffer_read(conn->_write_rb,(char*)write_samp,sizeof(sample_t)*nframes);
    }
    else
    {
        for(unsigned int i=0;i<nframes;i++) write_samp[i]=0.0;//buffer underrun
        DEVLOG_DEBUG("xrun in JackAudio::process");
    }


    // transport control
    if (conn->_use_transport) {

        conn->_transport_state = jack_transport_query(conn->_client, NULL);

        if (conn->_transport_state == JackTransportStopped && conn->_previous_transport_state == JackTransportRolling) {
            conn->_state_changed = true;
        }

        if (conn->_transport_state == JackTransportStarting && conn->_previous_transport_state != JackTransportStarting) {
            conn->_state_changed = true;
        }

        if (conn->_transport_state == JackTransportRolling && conn->_previous_transport_state != JackTransportRolling) {
            conn->_state_changed = true;
        }

        conn->_previous_transport_state = conn->_transport_state;
    }
    if(conn->_transport_command==TRANSPORT_START) jack_transport_start(conn->_client);
    if(conn->_transport_command==TRANSPORT_STOP) jack_transport_stop(conn->_client);
    if(conn->_transport_command==TRANSPORT_ZERO) {
        jack_position_t pos;
        pos.valid=(jack_position_bits_t)0;
        pos.frame=0;
        jack_transport_stop(conn->_client);
        jack_transport_reposition(conn->_client,&pos);
    }
    if(conn->_transport_command==TRANSPORT_STARTPOS)
    {
        jack_position_t pos;
        pos.valid=(jack_position_bits_t)0;
        pos.frame=conn->_startpos*conn->sampleRate();
        jack_transport_stop(conn->_client);
        jack_transport_reposition(conn->_client,&pos);
        jack_transport_start(conn->_client);
    }
    conn->_transport_command = 0;


    //notify
    uint64_t u = 1;
    if(write(conn->_eventfd, &u, sizeof(uint64_t))!=8) return 1;

    // no error
    return 0;
}

int JackAudio::sync(jack_transport_state_t state, jack_position_t *pos, void *arg)
{
    (void) state;

    //start tranport
    JackAudio* conn = (JackAudio*) arg;
    if(conn->_use_transport)
    {
        conn->_position =  pos->frame;
        return 1;
    }
    else return 1;


}

JackAudio::JackAudio(bool autoconnect)
{


    _client = jack_client_open("QTau",JackNoStartServer,NULL);
    
    if(_client==NULL) {
        QMessageBox msgbox;
        msgbox.setText("jack is not running");
        msgbox.exec();
        exit(1);
    }

    _buffer_size = jack_get_buffer_size(_client);

    //TODO configurable port names ???
    _write_port = jack_port_register(_client,"out",JACK_DEFAULT_AUDIO_TYPE,JackPortIsOutput,_buffer_size);
    _write_rb = jack_ringbuffer_create (sizeof(sample_t) * 4096);

    _midi_rb = jack_ringbuffer_create(4096);
    _midi_port = jack_port_register(_client,"midi_in",JACK_DEFAULT_MIDI_TYPE,JackPortIsInput,0);

    jack_set_process_callback(_client,process,this);
    jack_set_sync_callback(_client,sync,this);

    jack_activate(_client);

    if(autoconnect)
    {
        jack_connect(_client,"QTau:out","system:playback_2");//FOR testing
        jack_connect(_client,"QTau:out","system:playback_1");//FOR testing
    }

    _transport_command = TRANSPORT_ZERO;
    _use_transport = true;
    _playback = false;
}


int JackAudio::sampleRate()
{
    return jack_get_sample_rate(_client);
}

int JackAudio::writeData(void *framebuf, int bytes_per_frame)
{  
    if(jack_ringbuffer_write_space(_write_rb)>=(unsigned int)bytes_per_frame)
    {
        return jack_ringbuffer_write(_write_rb,(char*)framebuf,bytes_per_frame);
    }
    DEVLOG_DEBUG("xrun in JackAudio::writeData");//FIXME do not log while program startup
    return 0;//not data was written
}

float *JackAudio::allocateBuffer()
{
    if(_buffer_size==0) return NULL;
    return new float[_buffer_size];
}

void JackAudio::shutdown()
{
    jack_deactivate(_client);
    jack_client_close(_client);
}
